package top.mingyi4cjh.cms.controller;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.web.bind.annotation.*;
import top.mingyi4cjh.cms.common.configuration.AccessLimit;
import top.mingyi4cjh.cms.common.error.BusinessException;
import top.mingyi4cjh.cms.common.error.EmBusinessError;
import top.mingyi4cjh.cms.common.response.CommonReturnType;
import top.mingyi4cjh.cms.common.response.LoginResult;
import top.mingyi4cjh.cms.common.utils.EmRegisterType;
import top.mingyi4cjh.cms.common.utils.JwtUtils;
import top.mingyi4cjh.cms.common.utils.UserInfoUtils;
import top.mingyi4cjh.cms.model.UserLoginModel;
import top.mingyi4cjh.cms.model.UserModel;
import top.mingyi4cjh.cms.service.MailService;
import top.mingyi4cjh.cms.service.UserService;
import top.mingyi4cjh.cms.viewobject.UserVO;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import static top.mingyi4cjh.cms.common.utils.Field.LOGIN_USER;
import static top.mingyi4cjh.cms.common.utils.Field.USER_NAME;
import static top.mingyi4cjh.cms.common.utils.Util.replaceStr;

/**
 * @author MingYi
 * @program cms
 * @create 2022/04/21 23:34
 */
@Api
@RestController("user")
@RequestMapping(value = "/user", method = {RequestMethod.GET, RequestMethod.POST})
public class UserController extends BaseController {
    @Resource
    private UserService userService;

    @Resource
    private MailService mailService;

    @Resource
    private HttpServletRequest request;

    @Resource
    private UserInfoUtils userInfoUtils;

    /**
     * 用于登录的接口，将会记录用户登录的IP。
     * 在登录成功时会返回{"status":"success","data":null}，并将userId存储在cookie中，以便下次使用
     * 失败时会返回对应的错误码与错误信息
     *
     * @param userName 用户名
     * @param password 密码
     * @return {"status":"success","data":null}
     * @throws BusinessException 10001, 20009, 90001
     */
    @ApiOperation(value = "登录")
    @AccessLimit
    @PostMapping(value = "/login", consumes = {CONTENT_TYPE_FORMED})
    public LoginResult login(String userName, String password) throws BusinessException, NoSuchAlgorithmException {
        if (StringUtils.isEmpty(userName) || StringUtils.isEmpty(password)) {
            BusinessException e = new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR);
            logger.info("User {} log in fail, error message: {}", userName, e.getErrMsg());
            throw e;
        }
        if (!isLegalString(userName)) {
            throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR, "用户名不合规范");
        }

        // 登录验证
        UserModel userModel = userService.validateLogin(userName, password);
        userInfoUtils.setUserInfo(userModel.getUserId(), convertFromModel(userModel));
        // 生成token
        Map<String, String> payload = new HashMap<>();
        payload.put(LOGIN_USER, String.valueOf(userModel.getUserId()));
        String token = JwtUtils.getToken(payload);
        // 记录登陆记录
        UserLoginModel userLoginModel = new UserLoginModel();
        userLoginModel.setUserId(userModel.getUserId());
        userLoginModel.setIp(getIpInformation(request));
        userLoginModel.setLoginDate(new Date(System.currentTimeMillis()));
        userService.recordLogin(userLoginModel);
        logger.info("User {} log in success.", userName);
        return LoginResult.create(null, token);
    }

    /**
     * 用户注册的接口，会记录访问IP。
     * 失败时会返回错误码与错误信息。
     *
     * @param userName 用户名
     * @param password 密码
     * @param email    邮箱地址
     * @param gender   性别
     * @param age      年龄
     * @param otpCode  验证码
     * @return {"status":"success","data":null}
     * @throws NoSuchAlgorithmException 由加密时的MessageDigest.getInstance()抛出
     * @throws BusinessException        10001
     */
    @ApiOperation(value = "根据邮箱注册用户")
    @AccessLimit
    @PostMapping(value = "/registerByEmail", consumes = {CONTENT_TYPE_FORMED})
    public CommonReturnType registerByEmail(String userName,
                                            String password,
                                            String email,
                                            String gender,
                                            String age,
                                            String otpCode) throws NoSuchAlgorithmException, BusinessException {
        logger.debug("Username: {}, password: {}, email: {}, gender: {}, age: {}, otpCode: {}", userName, password, email, gender, age, otpCode);

        if (StringUtils.isEmpty(userName) || !isLegalString(userName)) {
            throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR, "用户名不合规范");
        }
        logger.info("User \"{}\" attempts to register by email using IP address [{}].", userName, getIpInformation(request));
        if (StringUtils.isEmpty(password) || StringUtils.isEmpty(email) || StringUtils.isEmpty(otpCode)) {
            BusinessException e = new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR);
            logger.info("User {} register fail, error message: {}", userName, e.getErrMsg());
            throw e;
        }

        // 整合对应数据
        UserModel userModel = new UserModel();
        userModel.setUserName(userName);
        userModel.setPassword(password);
        userModel.setEmail(email);
        userModel.setGender((byte) ("m".equals(gender) ? 1 : 2));
        userModel.setAge(Integer.valueOf(age));
        userModel.setRegisterTime(new Date(System.currentTimeMillis()));
        userModel.setRegisterTypeId(EmRegisterType.EMAIL.getTypeCode());
        // 注册
        userService.registerByEmail(userModel, otpCode);
        logger.info("User \"{}\" register success.", userName);
        // 清理Redis的ttl缓存
        mailService.cleanCache(email);
        return CommonReturnType.create(null);
    }

    /**
     * 检查userName和email能否匹配，能匹配则发送验证码
     *
     * @param userName 用户名
     * @param email    邮箱地址
     * @return {"status":"success","data":null}
     * @throws BusinessException 10001, 20005, 30003
     */
    @ApiOperation(value = "重新发送验证码")
    @AccessLimit
    @PostMapping(value = "/reGetEmailOtp", consumes = {CONTENT_TYPE_FORMED})
    public CommonReturnType reGetEmailOtpCode(String userName, String email) throws BusinessException {
        //入参校验
        if (StringUtils.isEmpty(userName) || StringUtils.isEmpty(email)) {
            BusinessException e = new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR);
            logger.warn("ReGetEmailOtp fail at IP [{}], error message: {}", getIpInformation(request), e.getErrMsg());
            throw e;
        }
        //检查userName-email是否匹配
        if (!userService.checkEmail(userName, email)) {
            BusinessException e = new BusinessException(EmBusinessError.EMAIL_CHECK_FAIL);
            logger.info("Access from IP [{}] check email fail.", getIpInformation(request));
            throw e;
        }

        // 检查是否频繁请求
        if (!mailService.checkTtl(email)) {
            BusinessException e = new BusinessException(EmBusinessError.SEND_FREQUENTLY_ERROR);
            logger.info("IP:{} access interface \"reGetEmailOtp\" frequently.", getIpInformation(request));
            throw e;
        }
        // 通过检查则发送验证码
        String otpCode = RandomStringUtils.randomNumeric(6);
        mailService.sendOtpCode(email, otpCode, true);
        logger.info("IP:{} access, resend otpCode {} to {}", getIpInformation(request), otpCode, email);
        return CommonReturnType.create(null);
    }

    /**
     * 直接发Otp给这个id对应的邮箱
     *
     * @return {"status":"success","data":null}
     * @throws BusinessException 10001, 30003
     */
    @ApiOperation(value = "向当前登录账号发送验证码")
    @AccessLimit
    @GetMapping(value = "/sendEmailOtp")
    public CommonReturnType reGetEmailOtpCode() throws BusinessException {
        String id = userInfoUtils.getLoginUserId(request);
        // 登录态检验
        if (id == null) {
            BusinessException e = new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR, "未登录访问");
            logger.warn("Not login user try to send otp for reset password from IP [{}].", getIpInformation(request));
            throw e;
        }
        // 读取email信息
        UserModel userModel = userService.getUserById(Long.valueOf(id));
        String email = userModel.getEmail();

        // 检测是否频繁发送
        if (!mailService.checkTtl(email)) {
            BusinessException e = new BusinessException(EmBusinessError.SEND_FREQUENTLY_ERROR);
            logger.info("IP:{} access interface \"sendEmailOtp\" frequently.", getIpInformation(request));
            throw e;
        }

        // 发送otpCode
        String otpCode = RandomStringUtils.randomNumeric(6);
        mailService.sendOtpCode(email, otpCode, true);
        logger.info("IP:{} access, resend otpCode {} to {}", getIpInformation(request), otpCode, email);

        // 写入redis， 需要一个新的方法，保存id和otp的对应关系
        userService.saveIdOtp(userModel, otpCode);

        return CommonReturnType.create(null);
    }

    /**
     * 只传入一个otpCode，获取session的登录的用户的id信息，对id-otpCode进行对比，以此来检查正在重置密码的是否为用户本人。
     *
     * @param otpCode 验证码
     * @return {"status":"success","data":null}
     * @throws BusinessException 10001
     */
    @ApiOperation(value = "检查重置密码状态")
    @AccessLimit
    @PostMapping(value = "/checkStatus", consumes = {CONTENT_TYPE_FORMED})
    public CommonReturnType checkIdentify(String otpCode) throws BusinessException {
        if (StringUtils.isEmpty(otpCode)) {
            throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR);
        }
        String id = userInfoUtils.getLoginUserId(request);
        if (id == null) {
            BusinessException e = new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR, "未登录访问");
            logger.warn("Not login user check status from IP [{}].", getIpInformation(request));
            throw e;
        }

        // 需要一个根据id和otpCode检验身份的方法，可以根据id从redis里取email，然后再检验
        userService.checkStatus(Long.valueOf(id), otpCode);

        return CommonReturnType.create(null);
    }

    /**
     * 检查身份，如果成功，则在session中存入userId
     *
     * @param email   邮箱地址
     * @param otpCode 邮箱验证码
     * @return {"status":"success","data":null}
     * @throws BusinessException 10001, 20001, 30001
     */
    @ApiOperation(value = "检查用户身份")
    @AccessLimit
    @PostMapping(value = "/checkIdentify", consumes = {CONTENT_TYPE_FORMED})
    public CommonReturnType checkIdentify(String email, String otpCode) throws BusinessException {
        //入参校验
        if (StringUtils.isEmpty(email) || StringUtils.isEmpty(otpCode)) {
            BusinessException e = new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR);
            logger.info("Check identify of IP [{}] fail.", getIpInformation(request));
            throw e;
        }

        if (!mailService.checkEmail(email)) {
            email = replaceStr(email);
            logger.warn("Wrong email format {} from ip [{}]", email, getIpInformation(request));
            throw new BusinessException(EmBusinessError.MAIL_FORMAT_ERROR);
        }
        UserModel userModel = userService.checkIdentify(email, otpCode);
        // 验证用户是否存在
        if (userModel == null) {
            BusinessException e = new BusinessException(EmBusinessError.USER_NOT_EXIST);
            logger.info("Not found user used email [{}], from IP [{}]", email, getIpInformation(request));
            throw e;
        }
        // 保存userId
        this.request.getSession().setAttribute(LOGIN_USER, userModel.getUserId());

        return CommonReturnType.create(null);
    }

    /**
     * 从session中获取userId，重置其password和salt
     * 注：找回密码操作，需要按顺序执行reGetEmailOtp -> checkIdentify -> resetPassword
     *
     * @param password 希望重置的新密码
     * @return {"status":"success","data":null}
     * @throws BusinessException 10001, 20006
     */
    @ApiOperation(value = "重置密码")
    @AccessLimit
    @PostMapping(value = "/resetPassword", consumes = {CONTENT_TYPE_FORMED})
    public CommonReturnType resetPassword(String password) throws BusinessException, NoSuchAlgorithmException {
        // 入参校验
        if (StringUtils.isEmpty(password)) {
            throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR, "新密码不能为空");
        }
        // 验证用户身份
        String id = userInfoUtils.getLoginUserId(request);
        if (id == null) {
            BusinessException e = new BusinessException(EmBusinessError.AUTHENTICATION_NOT_VERIFIED);
            logger.warn("Access from IP [{}] want to reset password but not checked.", getIpInformation(request));
            throw e;
        }

        // 重置密码
        userService.resetPassword(Long.valueOf(id), password);
        // 清理redis缓存
        userService.cleanCacheOfReset(Long.valueOf(id));
        return CommonReturnType.create(null);
    }

    /**
     * 生成otp，并发送到入参邮件处
     *
     * @param email 希望发送到的邮箱地址
     * @return {"status":"success","data":null}
     * @throws BusinessException 10001, 30003
     */
    @ApiOperation(value = "发送验证码到指定邮箱")
    @AccessLimit
    @GetMapping(value = "/getEmailOtp")
    public CommonReturnType getOtpCode(String email) throws BusinessException {
        if (StringUtils.isEmpty(email)) {
            throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR);
        }
        // 检查是否频繁请求
        if (!mailService.checkTtl(email)) {
            BusinessException e = new BusinessException(EmBusinessError.SEND_FREQUENTLY_ERROR);
            logger.info("IP:{} access interface \"getEmailOtp\" frequently.", getIpInformation(request));
            throw e;
        }
        // 发送Otp
        String otpCode = RandomStringUtils.randomNumeric(6);
        mailService.sendOtpCode(email, otpCode, false);
        email = replaceStr(email);
        logger.info("IP:{} access, send otpCode {} to {}", getIpInformation(request), otpCode, email);
        return CommonReturnType.create(null);
    }

    /**
     * 从session中获取登录的userId，根据该id返回对应的信息。
     *
     * @return userInfo
     * @throws BusinessException 10001, 20001
     */
    @ApiOperation(value = "获取自身信息")
    @AccessLimit
    @GetMapping(value = "/getInfo")
    public CommonReturnType getUserInfo() throws BusinessException {
        String id = userInfoUtils.getLoginUserId(request);
        if (id == null) {
            BusinessException e = new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR, "未登录访问");
            logger.warn("Not login user try to get someone's info from IP [{}].", getIpInformation(request));
            throw e;
        }
        UserModel userModel = userService.getUserById(Long.valueOf(id));
        if (userModel == null) {
            BusinessException e = new BusinessException(EmBusinessError.USER_NOT_EXIST, "获取用户信息失败");
            logger.info("User [{}] requested by IP [{}] does not exist.", id, getIpInformation(request));
            throw e;
        }
        UserVO userVO = convertFromModel(userModel);

        return CommonReturnType.create(userVO);
    }

    /**
     * 登出，删除该session中的登录态信息即可
     *
     * @return {"status":"success","data":null}
     */
    @ApiOperation(value = "登出")
    @AccessLimit
    @GetMapping(value = "/logout")
    public CommonReturnType logout() {
        request.getSession().removeAttribute(LOGIN_USER);
        request.getSession().removeAttribute(USER_NAME);
        return CommonReturnType.create(null);
    }

    /**
     * 将userModel的数据转换成可以传给前端的userVO类型
     *
     * @param userModel UserModel instance
     * @return UserVO instance
     */
    private UserVO convertFromModel(UserModel userModel) {
        if (userModel == null) {
            return null;
        }
        UserVO userVO = new UserVO();
        BeanUtils.copyProperties(userModel, userVO);
        return userVO;
    }
}